package com.hirsi.core.util;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.crypto.digest.MD5;
import cn.hutool.http.HttpRequest;
import cn.hutool.json.JSONObject;

import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.io.*;
import java.net.HttpCookie;
import java.util.Base64;
import java.util.List;

public class BaiduDiskUtil {

    //秒传大小为256k
    public final static int FILE_RAPID_MIN_SIZE = 262144;

    //分片大小为4M
    public final static int FILE_CHUNK_SIZE = 4194304;

    //普通用户单文件总大小上限为4GB
    public final static long FILE_SIZE_LIMIT = 4294967296L;

    //普通会员单文件总大小上限为10GB
    public final static long FILE_SIZE_MID = 10737418240L;

    //超级会员单文件总大小上限为20GB
    public final static long FILE_SIZE_MAX = 21474836480L;

    public final static String USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36";

    private static final String cookie = "PANWEB=1; BDUSS=A2OGUyQVYtdmE5S044NUxHWTBTOHJ3eC00ZlhQOVlreEh2UXlHUXdVV0lBREZsSVFBQUFBJCQAAAAAAAAAAAEAAAA4~572SGlyc2lfSGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIhzCWWIcwllSl; BDUSS_BFESS=A2OGUyQVYtdmE5S044NUxHWTBTOHJ3eC00ZlhQOVlreEh2UXlHUXdVV0lBREZsSVFBQUFBJCQAAAAAAAAAAAEAAAA4~572SGlyc2lfSGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIhzCWWIcwllSl; BIDUPSID=1CAE0DDAD625C0EEA0A14AD6BAFB6903; PSTM=1695118263; Hm_lvt_fa0277816200010a74ab7d2895df481b=1703666579; Hm_lvt_7a3960b6f067eb0085b7f96ff5e660b0=1704162082,1704507712,1705482597,1705912886; H_WISE_SIDS_BFESS=39996_40074_40079_40155; BAIDUID=1CAE0DDAD625C0EEA0A14AD6BAFB6903:SL=0:NR=10:FG=1; STOKEN=6c81d01ca7a618023e0ffefe167e62193ca5ec0f32049deb1752713be5212b4e; Hm_lvt_95fc87a381fad8fcb37d76ac51fefcea=1705911177,1705978114,1706164177,1706864346; H_PS_PSSID=40170_40200_40206_40212_40216_40224_40266; H_WISE_SIDS=40170_40200_40206_40212_40216_40224_40266; BAIDUID_BFESS=1CAE0DDAD625C0EEA0A14AD6BAFB6903:SL=0:NR=10:FG=1; delPer=0; PSINO=6; BA_HECTOR=80ah858481852l2l242k002l3r266e1itbaph1t; ZFY=iIsNkjBKZLP4bRtl42T2M:BRV2ksCLueUr:B9AUdHsjLY:C; BDORZ=B490B5EBF6F3CD402E515D22BCDA1598; csrfToken=Jq90ANAi6vPh83EZlkaW9j7B; PANPSC=10553568465182649998%3AHSTAF2XekfovGxnxQXpdS8Wn1hbHg1WZotdmVGqAS2qGsV9uK3jOeppXVAfLeXHbiy4ZCs9X3796g9ChlHbsrLTuTehkLKr4Ks%2BlyV4xxrIh7IX89yGaj8sD7Cfgfojr4guD%2BbN38S3R4SeWmT6baF9K9yVk1uvA4i2i1JdCg%2BQoj3hBKvZymPgT7Eg6UyTb; ndut_fmt=F8161DC21B9EA07D6D3E55B94FA932DF642F54E482A371D376DB9C468196DA42; ab_sr=1.0.1_ODdkNzFkNjVhNzgxNGQ1MThjOWJlYWM3OWFhYzJjMTVkYzc2ZmUzM2Y2NDRiNWRmYzBiNzFiMTIzMzFlY2MxZDZjMDc2NzUwYTljMDQzYjJhYmE0MzQxODE0ODgzNGViYzJmZTdlOThjZjY1NGJhNmE5NGE0YmYxMzY5MTgwZGNiMjZkOWQ1MmRkNWJmMmU4NGI1YjBlZGIxMzQyMWY4ZDY5Njk5ZGU4Y2Q1YjkwMTAyODllY2E0OGEzN2IyOWE5";
    private static int dpSortNum = 0;

    private static String bdstoken;

    private static Long uk;

    private static String logId;

    private static String perStr;

    /**
     * 初始化百度网盘
     * bdstoken、uk和logId等参数
     * bdstoken、uk是从loginStatus接口获取
     * logId是通过cookie中的BAIDUID计算出来的
     */
    public static void initBaidu() {
        List<HttpCookie> cookies = HttpCookie.parse(cookie);
        HttpCookie httpCookie = cookies.stream().filter(k -> "BAIDUID".equals(k.getName())).findFirst().orElse(null);
        if (httpCookie != null) logId = getLogId(httpCookie.getValue());
        loginStatus();
        perStr = RandomUtil.randomNumbers(6) + uk;
    }

    /**
     * 获取登录状态
     * @return
     */
    public static JSONObject loginStatus() {
        String body = HttpRequest.get("https://pan.baidu.com/api/loginStatus?clienttype=0&app_id=250528&web=1&channel=chunlei")
                .header("User-Agent", USER_AGENT)
                .cookie(cookie).execute().body();
        System.out.println(body);
        JSONObject object = new JSONObject(body);
        if (object.getInt("errno") == 0) {
            JSONObject loginInfo = object.getJSONObject("login_info");
            bdstoken = loginInfo.getStr("bdstoken");
            uk = loginInfo.getLong("uk");
        }
        return object;
    }

    /**
     * 预上传
     * @param path          目标文件路径
     * @param targetPath    目标文件夹
     * @param size          文件大小
     * @return
     */
    public static JSONObject preCreate(String path, String targetPath, Long size) {
        String dpLogId = perStr + String.format("%04d", dpSortNum++);
        String body = HttpRequest.post(String.format("https://pan.baidu.com/api/precreate?bdstoken=%s&app_id=250528&channel=chunlei&web=1&clienttype=0&dp-logid=%s", bdstoken, dpLogId))
                .header("User-Agent", USER_AGENT).cookie(cookie)
                .form("path", path).form("autoinit", 1)
                .form("target_path", targetPath)
                .form("block_list", size < FILE_CHUNK_SIZE ? " [\"5910a591dd8fc18c32a8f3df4fdc1761\"]" : "[\"5910a591dd8fc18c32a8f3df4fdc1761\",\"a5fc157d78e6ad1c7e114b056c92821e\"]")
                .form("local_mtime", System.currentTimeMillis() / 1000).execute().body();
        return new JSONObject(body);
    }

    /**
     * 分片上传文件
     * @param remotePath      上传文件路径
     * @param uploadId  上传id，preCreate中会返回
     * @param partSeq   分片文件下标
     * @param file      上传的分片文件
     * @return  会返回文件的md5
     */
    public static JSONObject superFile2(String remotePath, String uploadId, Integer partSeq, File file) {
        String dpLogId = perStr + String.format("%04d", dpSortNum++);
        String body = HttpRequest.post(String.format("https://xafj-ct10.pcs.baidu.com/rest/2.0/pcs/superfile2?method=upload&logid=%s&app_id=250528&channel=chunlei&web=1&clienttype=0&path=%s&uploadid=%s&uploadsign=0&partseq=%s&dp-logid=%s", logId, remotePath, uploadId, partSeq, dpLogId))
                .header("User-Agent", USER_AGENT).cookie(cookie)
                .form("file", file)
                .execute().body();
        return new JSONObject(body);
    }

    /**
     * 上传完成后创建文件
     *
     * @param path       上传后的路径
     * @param uploadId   上传id
     * @param targetPath 目标文件夹
     * @param blockList  分片上传的文件md5，多个json格式，小于等于4M的非分片上传的，就只有1个md5
     * @return
     */
    public static JSONObject createFile(String path, Long size, String uploadId, String targetPath, String blockList) {
        String dpLogId = perStr + String.format("%04d", dpSortNum++);
        String body = HttpRequest.post(String.format("https://pan.baidu.com/api/create?isdir=0&bdstoken=%s&app_id=250528&channel=chunlei&web=1&clienttype=0&dp-logid=%s", bdstoken, dpLogId))
                .header("User-Agent", USER_AGENT).cookie(cookie)
                .form("path", path).form("size", size).form("uploadid", uploadId)
                .form("target_path", targetPath).form("block_list", blockList)
                .form("local_mtime", System.currentTimeMillis() / 1000).execute().body();
        return new JSONObject(body);
    }

    /**
     * 文件秒传
     * @param uploadId      上传id
     * @param path          上传后的路径
     * @param file          源文件
     * @param targetPath    目标路径
     * @return
     */
    public static JSONObject rapidUpload(String uploadId, String path, File file, String targetPath) {
        String dpLogId = RandomUtil.randomNumbers(6) + uk + String.format("%04d", dpSortNum++);
        String fileMd5 = MD5.create().digestHex(file);
        String contentMd5, sliceMd5, dataContent;
        long dataTime = System.currentTimeMillis() / 1000;
        long dataOffset;
        try {
            contentMd5 = runJs("encryptMd5", fileMd5);
            RandomAccessFile raf = new RandomAccessFile(file, "r");
            byte[] bytes = new byte[BaiduDiskUtil.FILE_RAPID_MIN_SIZE];
            raf.read(bytes);
            sliceMd5 = runJs("encryptMd5", MD5.create().digestHex(bytes));
            dataOffset = Convert.toInt(runJs("getOffset", uk, dataTime, contentMd5, file.length()));
            dataContent = splitToBase64(file, dataOffset);
        } catch (ScriptException | NoSuchMethodException | IOException e) {
            throw new RuntimeException(e);
        }
        String body = HttpRequest.post(String.format("https://pan.baidu.com/api/rapidupload?rtype=1&bdstoken=%s&app_id=250528&channel=chunlei&web=1&clienttype=0&dp-logid=%s", bdstoken, dpLogId))
                .header("User-Agent", USER_AGENT).cookie(cookie)
                .form("uploadid", uploadId).form("path", path).form("target_path", targetPath)
                .form("content-length", file.length()).form("content-md5", contentMd5).form("slice-md5", sliceMd5)
                .form("local_mtime", System.currentTimeMillis() / 1000).form("data_time", dataTime)
                .form("data_offset", dataOffset).form("data_content", dataContent).execute().body();
        return new JSONObject(body);
    }

    public static String getLogId(String baiduId) {
        try {
            return runJs("getLogId", baiduId);
        } catch (ScriptException | NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }

    private static <T> T runJs(String method, Object... args) throws ScriptException, NoSuchMethodException {
        ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
        ScriptEngine engine = scriptEngineManager.getEngineByName("JavaScript");
        engine.eval(FileUtil.readString(BaiduDiskUtil.class.getClassLoader().getResource("js/BaiduJs.js"), CharsetUtil.UTF_8));
        Invocable env = (Invocable) engine;
        return (T) env.invokeFunction(method, args);
    }

    private static String splitToBase64(File file, long start) {
        try (RandomAccessFile raf = new RandomAccessFile(file, "r")) {
            byte[] buffer = new byte[BaiduDiskUtil.FILE_RAPID_MIN_SIZE];
            raf.seek(start); // 移动文件指针到起始位置
            raf.readFully(buffer); // 读取指定长度的字节
            return Base64.getEncoder().encodeToString(buffer);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    static {
        initBaidu();
    }

    public static void main(String[] args) {
        ///loginStatus();
    }
}
